/* File Server
 */

package main

import (
  "fmt"
  "net/http"
  "os"
  "flag"
  "regexp"
  "strings"
	"strconv"
  "net"
//  "math/rand"
  "time"
  "encoding/json"
  "log"
  "io"
	"errors"
	"bytes"
  "zsystem/webagent"
  "zsystem/maintain"
	"os/signal"
	"syscall"
)

var (
  bindAddress       *string = flag.String("BindAddress", "unix:/tmp/webagent.sock", "Listen address. Format: <connection type>:<address>.\r\n\tFor TCP: tcp:<IP>:<PORT>;\r\n\tFor Unix domain socket: unix:<socket file>.") 
  proxyRoot					*string = flag.String("Root", "/var/zsystem/webagent/http", "The HTTP root address for web agent.") 
  redisAddress			*string = flag.String("RedisAddress", "127.0.0.1:6379", "The address for redis server.") 
)

const (
  programName = "kx_webagent"
  version     = "0.0.1"
)

var g_data *webagent.WebAgentData
var g_listener net.Listener
var g_unixListener *net.UnixListener
var g_tcpListener *net.TCPListener
var g_bindAddressType string
var g_bindAddress string

// Regular Http handler
type route struct {
  pattern *regexp.Regexp
  handler http.Handler
}

type RegexpHandler struct {
  routes []*route
}

func (h *RegexpHandler) Handler(pattern *regexp.Regexp, handler http.Handler) {
  h.routes = append(h.routes, &route{pattern, handler})
}

func (h *RegexpHandler) HandleFunc(pattern *regexp.Regexp, handler func(http.ResponseWriter, *http.Request)) {
  h.routes = append(h.routes, &route{pattern, http.HandlerFunc(handler)})
}

func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
/*  fmt.Println("path: ", r.URL.Path)
  {
    proxyReg, err := regexp.Compile("/proxy/*.")
    if err == nil {
      if proxyReg.MatchString(r.URL.Path) {
        fmt.Println("Macth")
      } else {
        fmt.Println("Unmacth")
      }
    }
  }
  fmt.Printf("h.routes: %+v\n", h.routes)
*/
  for _, route := range h.routes {
    if route.pattern.MatchString(r.URL.Path) {
//      fmt.Printf("matched: %+v\n", route)
      route.handler.ServeHTTP(w, r)
      return
    }
  }
  // no pattern matched; send 404 response
  http.NotFound(w, r)
}

func signalCatcher() {
	ch := make(chan os.Signal)
	signal.Notify(ch, syscall.SIGINT)
	sig := <-ch
	fmt.Printf("Signal received: %v\n", sig)
	if g_tcpListener != nil {
		g_tcpListener.Close()
	}
	if g_unixListener != nil {
		g_unixListener.Close()
	}
	os.Exit(0)
}

func safeRemoveSocketFile(addr string) error {
	fi, err := os.Lstat(addr)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		return err
	}
	// File exist
	fmt.Printf("Mode: %+v\n", fi)
	// Try to connect it
	saddr := &net.UnixAddr{
		Net: "unix",
		Name: addr,
	}
	var conn *net.UnixConn
	conn, err = net.DialUnix("unix", nil, saddr)
	if err != nil {
		// Connect failed.
		// This file should been remained by pre. abnormally terminated program.
		// Try to remove it.
		err = os.Remove(addr)
		return err
	}

	// There is an other program in use.
	conn.Close()
	return errors.New("An other program is running")
}

func safeListenOnUnixSocket(addr string) (listener *net.UnixListener, err error) {
	err = safeRemoveSocketFile(addr)
	if err != nil {
		return
	}
	laddr := net.UnixAddr{
		Net: "unix",
		Name: addr,
	}
	return net.ListenUnix("unix", &laddr)
}

func main() {
  flag.Usage = func() {
    fmt.Fprintf(os.Stderr, "%s version[%s]\r\nUsage: %s [OPTIONS]\r\n", programName, version, os.Args[0])
    flag.PrintDefaults()
  }
  flag.Parse()

	var err error
	cmdChan := make(chan string)
  g_data, err = webagent.Conn(*redisAddress, &cmdChan)
  if err != nil { log.Fatal("webagent.Conn failed") }

	addresses := strings.SplitN(*bindAddress, ":", 2)
	if len(addresses) < 2 { log.Fatal("Argument BindAddress error") }
	g_bindAddressType = strings.ToLower(addresses[0])
	g_bindAddress = addresses[1]
	switch g_bindAddressType{
		case "unix":
			g_unixListener, err = safeListenOnUnixSocket(g_bindAddress)
			checkError(err)
			defer g_unixListener.Close()
			g_listener = g_unixListener
		case "tcp":
			var addr *net.TCPAddr
			addr, err = net.ResolveTCPAddr("tcp", g_bindAddress)
			checkError(err)
			g_tcpListener, err = net.ListenTCP("tcp", addr)
			checkError(err)
			defer g_tcpListener.Close()
			g_listener = g_tcpListener
		default:
			log.Fatal("Argument BindAddress error")
	}

  fileServer := http.FileServer(http.Dir(*proxyRoot))
  var rh RegexpHandler
  var rootReg  *regexp.Regexp
  var proxyReg *regexp.Regexp
  var exitReg *regexp.Regexp
  var roomReg *regexp.Regexp
  proxyReg, err = regexp.Compile("/proxy/*.")
  roomReg, err = regexp.Compile("/room/*.")
  exitReg, err = regexp.Compile("/exit")
  rootReg, err = regexp.Compile("/*.")
  rh.HandleFunc(proxyReg, proxyCB)
  rh.HandleFunc(roomReg, roomCB)
  rh.HandleFunc(exitReg, exitCB)
  rh.Handler(rootReg, fileServer)

//  http.Handle("/", &rh)

  go g_data.MainLoop()

  server :=  &http.Server{
    Addr:         *bindAddress,
    Handler:      &rh,
    ReadTimeout:  30*time.Second,
    WriteTimeout: 30*time.Second,
  }

	go signalCatcher()
  err = server.Serve(g_listener)
  checkError(err)
}

func checkError(err error) {
  if err != nil {
    fmt.Println("Fatal error ", err.Error())
    os.Exit(1)
  }
}

func proxyCB(writer http.ResponseWriter, req *http.Request) {
//  fmt.Printf("proxy(%+v)\n", req)
  rooms := strings.Split(strings.Replace(req.URL.Path, "/proxy/", "", 1), "&")
  roomMap := g_data.GetRooms(&rooms)

  var conn net.Conn
  var err error
  conn, _, err = writer.(http.Hijacker).Hijack()
  if err != nil {
    log.Println("Decode proxy request failed! error: ", err)
    return
  }
  defer conn.Close()

  for _, room := range *roomMap {
    if room.Server != nil {
      proxyToRoom(&conn, req, room)
    }
  }
  writer.Write(nil)
}

func proxyToRoom(connSrc *net.Conn, req *http.Request, room *maintain.Room) {
  // Connect room
  connDest, err := net.Dial("tcp", (*room).Server.InAddr)
  if err != nil {
    log.Println("proxyToRoom() net.Dail(", (*room).Server.InAddr, ") failed!")
  }
  go func() {
    io.Copy(connDest, *connSrc)
    connDest.Close()
  }()
}

func exitCB(writer http.ResponseWriter, req *http.Request) {
  writer.Write([]byte("OK"))
  log.Println("To Exit")
	fmt.Printf("cmd: %+v", (*(*g_data).CmdChan))
	(*(*g_data).CmdChan) <- "exit"
//  (*g_data.CmdChan) <- "exit"
  log.Println("Waiting...")
  for g_data.ExitFlags != webagent.EXIT_FLAG_ALL {
    time.Sleep(time.Second)
  }
  log.Fatal("Exit")
}

type roomDetail struct {
	Rid int
	Addr string
}

func unsealServer(serverAddr string, roomId int) error {
	resp, err := http.Post("http://"+serverAddr+"/unseal", "text", bytes.NewBufferString("{\"Rid\":" + strconv.Itoa(roomId) + "}"))
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(fmt.Sprintf("Unseal failed! %+v", resp))
	}
	return nil
}

func roomCB(writer http.ResponseWriter, req *http.Request) {
//  fmt.Printf("proxy(%+v)\n", req)
  roomId, err := strconv.Atoi(strings.Replace(req.URL.Path, "/room/", "", 1))
	if err != nil || roomId <= 0 {
		log.Println("roomCB(), parse room Id Error")
		http.NotFound(writer, req)
		return
	}
  room, isNew := g_data.GetRoom(roomId)
	if room == nil || room.Server == nil {
		log.Println("roomCB(), Room server is empty!")
		http.Error(writer, "Server is nil", http.StatusInternalServerError)
		return
	}
	if isNew {
		err = unsealServer(room.Server.InAddr, roomId)
		if err != nil {
			log.Println("unsealServer failed!error:", err)
		}
	}
	b, err := json.Marshal(roomDetail{room.Id, room.Server.ExAddr})
	if err != nil {
		log.Println("roomCB(), Marshal error!")
		http.Error(writer, "Error", http.StatusInternalServerError)
		return
	}
  writer.Write(b)
}
